﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using LingDong.IndexReader;
using NLog;

namespace LingDong.LinkAnalyser
{
    /// <summary>
    /// A basic implementation of traditional google's page rank algotithm
    /// This class can calculate both page rank and site rank
    /// For main memory is limit, adjacency matrix would not be load in memory at once
    /// Howerer the entire dictionary of [url, id] should be stored in memory
    /// </summary>
    internal class GGPageRankAlgorithm
    {
        protected void ComputeRank()
        {
            // PR(n) = 1 / N
            double initPageRank = 1.0 / (double)count;
            // I(n) = alfa / N
            double[] I = new double[count];
            for (int i = 0; i < count; i++)
            {
                rank[i] = initPageRank;
            }

            // sum of page rank which doc's outdegree are 0
            double zeroPRSum = 0.0;

            using (FileStream fs = new FileStream(outLinkTempFile, FileMode.Open))
            {
                // iterator to compute page rank
                for (int i = 0; i < iteratorCount; i++)
                {
                    ReSetZeroPRSumAndI(ref zeroPRSum, ref I);
                    ComputePageRankIterator(fs, I, zeroPRSum);
                }
            }
            File.Delete(outLinkTempFile);
        }

        private void ReSetZeroPRSumAndI(ref double zeroPRSum, ref double[] I)
        {
            zeroPRSum = 0.0;
            double initIN = alfa / (double)count;
            for (int i = 0; i < count; i++)
            {
                I[i] = initIN;
                if (outDegree[i] == 0)
                    zeroPRSum += rank[i];
            }
            zeroPRSum += (1.0 - rank.Sum()) / (1 - alfa);
        }

        private void ComputePageRankIterator(FileStream fs, double[] I, double zeroPRSum)
        {
            for (int i = 0; i < count; i++)
            {
                fs.Position = outLinksOffset[i];
                List<int> outDocIDList = SerializeProvider.Serializer.Deserialize<List<int>>(fs);
                if (outDocIDList == null)
                    continue;

                //Debug.Assert(outDegree[i] != 0);
                double contributePR = (1 - alfa) * rank[i] / outDegree[i];
                foreach (int id in outDocIDList)
                {
                    I[id] += contributePR;
                }
            }

            double zeroPRSumContribute = (1 - alfa) * zeroPRSum / count;
            for (int i = 0; i < count; i++)
            {
                rank[i] = I[i] + zeroPRSumContribute;
            }
        }

        protected void LogSummary(GetNameByID getName)
        {
            Logger logger = LogManager.GetCurrentClassLogger();
            logger.Trace("PageRank summry: {0}", this.GetType().Name);

            logger.Trace("Total out degree: {0}", outDegree.Sum());
            logger.Trace("Total in degree: {0}", inDegree.Sum());
            logger.Trace("Total rank: {0}", rank.Sum());

            logger.Trace("Top 10 pages:");
            double bar = rank.OrderByDescending(r => r).ElementAt(10);
            int len = rank.Length;
            for (int i = 0; i < len; i++)
            {
                if (rank[i] >= bar)
                {
                    logger.Trace("ID: {0}, PR: {1}, InDegree:{2}, OutDegree: {3}, URL: {4}",
                                   i, rank[i], inDegree[i], outDegree[i], getName(i));
                }
            }

            logger.Trace("Top 10 indegrees:");
            int barDeg = inDegree.OrderByDescending(deg => deg).ElementAt(10);
            for (int i = 0; i < len; i++)
            {
                if (inDegree[i] >= barDeg)
                {
                    logger.Trace("ID: {0}, PR: {1}, InDegree:{2}, OutDegree: {3}, URL: {4}",
                                   i, rank[i], inDegree[i], outDegree[i], getName(i));
                }
            }
        }

        protected delegate string GetNameByID(int id);

        protected Index index;

        protected string outLinkTempFile;
        /// <summary>
        /// [id, offset]
        /// </summary>
        protected Dictionary<int, long> outLinksOffset;

        /// <summary>
        /// document count or site count
        /// </summary>
        protected int count;

        protected int[] inDegree;
        protected int[] outDegree;

        protected double[] rank;

        // algorithm parameters
        private int iteratorCount = 10;
        private double alfa = 0.15;
    }
}
